package com.ruoyi.huanjing.controller;

import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.huanjing.domain.RegionInfo;
import com.ruoyi.huanjing.entity.Atmospheric;
import com.ruoyi.huanjing.service.impl.RegionInfoServiceImpl;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import ucar.ma2.Array;
import ucar.ma2.Index;
import ucar.ma2.IndexIterator;
import ucar.ma2.InvalidRangeException;
import ucar.nc2.Attribute;
import ucar.nc2.Dimension;
import ucar.nc2.NetcdfFile;
import ucar.nc2.Variable;

import java.io.IOException;
import java.math.BigDecimal;
import java.text.ParseException;
import java.util.*;
import java.text.SimpleDateFormat;

import static com.ruoyi.common.core.domain.AjaxResult.success;

@RestController
public class SeaFaceController {


    @Autowired
    private RegionInfoServiceImpl regionInfoService;
    /**
     * 读取指定 valid_time 下的所有经纬度的 热力图 值
     *
     * @return 包含所有经纬度对应的 热力图 值的列表
     * @throws IOException 如果读取文件时发生错误
     */
    @Anonymous
    @PostMapping("/readhaimianrelitu")
    public AjaxResult readSeaFaceHeatmap(@RequestBody Map<String, Object> requestBody) throws IOException, InvalidRangeException {
        String filePath = Objects.requireNonNull(requestBody.get("filePath"), "filePath不能为空").toString();
        int timeIndex = Integer.parseInt(Objects.requireNonNull(requestBody.get("timeIndex"), "timeIndex不能为空").toString());
        String val = Objects.requireNonNull(requestBody.get("val"), "val不能为空").toString();
        return success(readhaimianrelitu(filePath, timeIndex, val));
    }


    private Map<String, Object> readhaimianrelitu(String filePath, int timeIndex, String val) throws IOException, ucar.ma2.InvalidRangeException {
        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
//            Variable validTimeVar = ncFile.findVariable("valid_time");
            Variable validTimeVar = Arrays.stream(new String[]{
                            "valid_time",
                            "time"})
                    .map(ncFile::findVariable)
                    .filter(Objects::nonNull)
                    .findFirst()
                    .orElse(null);

            Variable latVar = ncFile.findVariable("latitude");
            Variable lonVar = ncFile.findVariable("longitude");
            Variable tempVar = ncFile.findVariable(val);

            Array latData = latVar.read();
            Array lonData = lonVar.read();

            // 1. 计算原始经纬度间隔
            double lat0 = latData.getDouble(0);
            double lat1 = latData.getDouble(1);
            double lon0 = lonData.getDouble(0);
            double lon1 = lonData.getDouble(1);

            double latSpacing = Math.abs(lat1 - lat0);  // 纬度间隔
            double lonSpacing = Math.abs(lon1 - lon0);  // 经度间隔

            // 2. 计算采样率，使最终间隔接近 0.25 度
            int sampleRateLat = (int) Math.round(0.25 / latSpacing);
            int sampleRateLon = (int) Math.round(0.25 / lonSpacing);

            // 3. 确保采样率至少为1（避免除零）
            sampleRateLat = Math.max(sampleRateLat, 1);
            sampleRateLon = Math.max(sampleRateLon, 1);

            int latSize = (int) latData.getShape()[0];
            int lonSize = (int) lonData.getShape()[0];

            // 4. 计算降采样后的目标大小
            int targetLatSize = latSize / sampleRateLat;
            int targetLonSize = lonSize / sampleRateLon;

            // 5. 确保至少有一个点
            targetLatSize = Math.max(targetLatSize, 1);
            targetLonSize = Math.max(targetLonSize, 1);

            // 6. 读取降采样后的数据
            int[] origin = new int[]{timeIndex, 0, 0};
            int[] shape = new int[]{1, targetLatSize, targetLonSize};
            Array tempData = tempVar.read(origin, shape);

            List<Atmospheric> tempDataList = new ArrayList<>();
            // 添加边界计算变量
            double minLat = Double.MAX_VALUE;
            double maxLat = Double.MIN_VALUE;
            double minLon = Double.MAX_VALUE;
            double maxLon = Double.MIN_VALUE;
            // 新增：数据值范围变量
            double minVal = Double.MAX_VALUE;
            double maxVal = Double.MIN_VALUE;

            Index index = tempData.getIndex();

            for (int la = 0; la < targetLatSize; la++) {
                for (int lo = 0; lo < targetLonSize; lo++) {
                    double temp = tempData.getDouble(index.set(0, la, lo));
                    if (!Double.isNaN(temp)) {
                        // 计算原始索引
                        int originalLa = la * sampleRateLat;
                        int originalLo = lo * sampleRateLon;

                        // 确保不超出范围
                        originalLa = Math.min(originalLa, latSize - 1);
                        originalLo = Math.min(originalLo, lonSize - 1);

                        double currentLat = latData.getDouble(originalLa);
                        double currentLon = lonData.getDouble(originalLo);

                        // 更新边界值
                        minLat = Math.min(minLat, currentLat);
                        maxLat = Math.max(maxLat, currentLat);
                        minLon = Math.min(minLon, currentLon);
                        maxLon = Math.max(maxLon, currentLon);

                        // 新增：更新数据值范围
                        minVal = Math.min(minVal, temp);
                        maxVal = Math.max(maxVal, temp);

                        Atmospheric atmospheric = new Atmospheric()
                                .setLon(BigDecimal.valueOf(currentLon))
                                .setLat(BigDecimal.valueOf(currentLat))
                                .setValue(BigDecimal.valueOf(temp))
                                .setTime(BigDecimal.valueOf(validTimeVar.read().getDouble(timeIndex)));

                        tempDataList.add(atmospheric);
                    }
                }
            }

            // 构建返回结果Map
            Map<String, Object> result = new HashMap<>();
            result.put("dataList", tempDataList);

            Map<String, Double> bounds = new HashMap<>();
            bounds.put("minLon", minLon);
            bounds.put("maxLon", maxLon);
            bounds.put("minLat", minLat);
            bounds.put("maxLat", maxLat);
            result.put("bounds", bounds);

            // 新增：添加数据值范围
            Map<String, Double> valueRange = new HashMap<>();
            valueRange.put("minValue", minVal);
            valueRange.put("maxValue", maxVal);
            result.put("valueRange", valueRange);

            return result;
        } catch (IOException e) {
            throw e;
        }
    }

    /*读取部分区域的热力图*/
    @Anonymous
    @PostMapping("/readhaimianrelituRegion")
    public AjaxResult readSeaFaceHeatmapRegion(@RequestBody Map<String, Object> requestBody) throws IOException, InvalidRangeException {
        String filePath = Objects.requireNonNull(requestBody.get("filePath"), "filePath不能为空").toString();
        int timeIndex = Integer.parseInt(Objects.requireNonNull(requestBody.get("timeIndex"), "timeIndex不能为空").toString());
        String val = Objects.requireNonNull(requestBody.get("val"), "val不能为空").toString();
        Long regionId = Long.parseLong(Objects.requireNonNull(requestBody.get("regionId"), "区域不能为空").toString());
        return success(readhaimianrelituRegion(filePath, timeIndex, val, regionId));
    }


    private Map<String, Object> readhaimianrelituRegion(String filePath, int timeIndex, String val, Long regionId) throws IOException, ucar.ma2.InvalidRangeException {
        // 获取区域信息及经纬度范围
        RegionInfo regionInfo = regionInfoService.selectRegionInfoById(regionId);
        String latLngRange = regionInfo.getLatLngRange();

        // 解析区域边界坐标，计算经纬度范围（min/max lat/lng）
        double minLat = Double.MAX_VALUE;
        double maxLat = Double.MIN_VALUE;
        double minLng = Double.MAX_VALUE;
        double maxLng = Double.MIN_VALUE;

        try {
            // 解析JSON格式的经纬度范围
            JSONArray jsonArray = new JSONArray(latLngRange);
            for (int i = 0; i < jsonArray.length(); i++) {
                JSONObject pointObj = jsonArray.getJSONObject(i);
                double lat = pointObj.getDouble("lat");
                double lng = pointObj.getDouble("lng");

                // 更新区域经纬度边界
                minLat = Math.min(minLat, lat);
                maxLat = Math.max(maxLat, lat);
                minLng = Math.min(minLng, lng);
                maxLng = Math.max(maxLng, lng);
            }
        } catch (JSONException e) {
            throw new IllegalArgumentException("区域经纬度格式解析错误: " + latLngRange, e);
        }

        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
//            Variable validTimeVar = ncFile.findVariable("valid_time");
            Variable validTimeVar = Arrays.stream(new String[]{
                            "valid_time",
                            "time"
                    })
                    .map(ncFile::findVariable)
                    .filter(Objects::nonNull)
                    .findFirst()
                    .orElse(null);

            Variable latVar = ncFile.findVariable("latitude");
            Variable lonVar = ncFile.findVariable("longitude");
            Variable tempVar = ncFile.findVariable(val);

            Array latData = latVar.read();
            Array lonData = lonVar.read();

            // 转换为double数组便于索引查找
            double[] latArray = extractDoubleArray(latData);
            double[] lonArray = extractDoubleArray(lonData);

            // 查找经纬度数组中对应区域边界的最近索引
            int minLatIdx = findNearestIndex(latArray, minLat);
            int maxLatIdx = findNearestIndex(latArray, maxLat);
            int minLonIdx = findNearestIndex(lonArray, minLng);
            int maxLonIdx = findNearestIndex(lonArray, maxLng);

            // 调整索引顺序（确保min在前，max在后）
            if (minLatIdx > maxLatIdx) {
                int temp = minLatIdx;
                minLatIdx = maxLatIdx;
                maxLatIdx = temp;
            }
            if (minLonIdx > maxLonIdx) {
                int temp = minLonIdx;
                minLonIdx = maxLonIdx;
                maxLonIdx = temp;
            }

            // 1. 计算原始经纬度间隔
            double lat0 = latArray[0];
            double lat1 = latArray[1];
            double lon0 = lonArray[0];
            double lon1 = lonArray[1];

            double latSpacing = Math.abs(lat1 - lat0);  // 纬度间隔
            double lonSpacing = Math.abs(lon1 - lon0);  // 经度间隔

            // 2. 计算采样率，使最终间隔接近 0.25 度
            int sampleRateLat = (int) Math.round(0.25 / latSpacing);
            int sampleRateLon = (int) Math.round(0.25 / lonSpacing);

            // 3. 确保采样率至少为1（避免除零）
            sampleRateLat = Math.max(sampleRateLat, 1);
            sampleRateLon = Math.max(sampleRateLon, 1);

            int latSize = latArray.length;
            int lonSize = lonArray.length;

            // 4. 计算降采样后的目标大小
            int targetLatSize = latSize / sampleRateLat;
            int targetLonSize = lonSize / sampleRateLon;

            // 5. 确保至少有一个点
            targetLatSize = Math.max(targetLatSize, 1);
            targetLonSize = Math.max(targetLonSize, 1);

            // 修改：仅读取区域范围内的数据
            int[] origin = new int[]{timeIndex, minLatIdx, minLonIdx};
            int[] shape = new int[]{1, maxLatIdx - minLatIdx + 1, maxLonIdx - minLonIdx + 1};
            Array tempArray = tempVar.read(origin, shape);

            List<Atmospheric> tempDataList = new ArrayList<>();
            // 添加边界计算变量
            double minLatResult = Double.MAX_VALUE;
            double maxLatResult = Double.MIN_VALUE;
            double minLonResult = Double.MAX_VALUE;
            double maxLonResult = Double.MIN_VALUE;
            // 新增：数据值范围变量
            double minVal = Double.MAX_VALUE;
            double maxVal = Double.MIN_VALUE;

            Index index = tempArray.getIndex();

            for (int la = 0; la < shape[1]; la++) {
                // 计算全局纬度索引
                int globalLa = minLatIdx + la;
                double currentLat = latArray[globalLa];

                for (int lo = 0; lo < shape[2]; lo++) {
                    // 计算全局经度索引
                    int globalLo = minLonIdx + lo;
                    double currentLon = lonArray[globalLo];

                    double temp = tempArray.getDouble(index.set(0, la, lo));

                    if (!Double.isNaN(temp)) {
                        // 更新边界值
                        minLatResult = Math.min(minLatResult, currentLat);
                        maxLatResult = Math.max(maxLatResult, currentLat);
                        minLonResult = Math.min(minLonResult, currentLon);
                        maxLonResult = Math.max(maxLonResult, currentLon);
                        minVal = Math.min(minVal, temp);
                        maxVal = Math.max(maxVal, temp);

                        Atmospheric atmospheric = new Atmospheric()
                                .setLon(BigDecimal.valueOf(currentLon))
                                .setLat(BigDecimal.valueOf(currentLat))
                                .setValue(BigDecimal.valueOf(temp))
                                .setTime(BigDecimal.valueOf(validTimeVar.read().getDouble(timeIndex)));

                        tempDataList.add(atmospheric);
                    }
                }
            }

            // 构建返回结果Map
            Map<String, Object> result = new HashMap<>();
            result.put("dataList", tempDataList);

            Map<String, Double> bounds = new HashMap<>();
            bounds.put("minLon", minLonResult);
            bounds.put("maxLon", maxLonResult);
            bounds.put("minLat", minLatResult);
            bounds.put("maxLat", maxLatResult);
            result.put("bounds", bounds);

            Map<String, Double> valueRange = new HashMap<>();
            valueRange.put("minValue", minVal);
            valueRange.put("maxValue", maxVal);
            result.put("valueRange", valueRange);

            return result;
        } catch (IOException e) {
            throw e;
        }
    }


    /**
     * 读取指定lon 和 lat 下不同 time 的 value 值，输入为具体值
     *
     * @return 包含不同 height 对应的 value 值的列表
     * @throws IOException 如果读取文件时发生错误
     */
    @Anonymous
    @PostMapping("/readhaimiankuoxiantu")
    public AjaxResult readSeaFaceChart(@RequestBody Map<String, Object> requestBody) throws IOException, InvalidRangeException {
        String filePath = Objects.requireNonNull(requestBody.get("filePath"), "filePath不能为空").toString();
        double targetLon = Double.parseDouble(Objects.requireNonNull(requestBody.get("targetLon"), "targetLon不能为空").toString());
        double targetLat = Double.parseDouble(Objects.requireNonNull(requestBody.get("targetLat"), "targetLat不能为空").toString());
        String val = Objects.requireNonNull(requestBody.get("val"), "val不能为空").toString();
        List<Integer> timeIndexList = (List<Integer>) requestBody.get("timeIndexList");
        return success(readhaimiankuoxiantu(filePath, targetLon, targetLat, val, timeIndexList));

    }

    private Map<String,Object> readhaimiankuoxiantu(String filePath, double targetLon, double targetLat, String val, List<Integer> timeIndexList) throws IOException, ucar.ma2.InvalidRangeException {
        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
            // 1. 获取变量并校验
//            Variable timeVar = ncFile.findVariable("valid_time");
            Variable timeVar = Arrays.stream(new String[]{
                            "valid_time",
                            "time"})
                    .map(ncFile::findVariable)
                    .filter(Objects::nonNull)
                    .findFirst()
                    .orElse(null);
            Variable latVar = ncFile.findVariable("latitude");
            Variable lonVar = ncFile.findVariable("longitude");
            Variable tempVar = ncFile.findVariable(val);
            validateVariables(timeVar, latVar, lonVar, tempVar);

            // 2. 读取经纬度数据并查找索引
            double[] lonArray = extractDoubleArray(lonVar.read());
            double[] latArray = extractDoubleArray(latVar.read());
            int lonIndex = findNearestIndex(lonArray, targetLon);
            int latIndex = findNearestIndex(latArray, targetLat);

            // 3. 解析时间单位和日历属性（用于格式化时间）
            String units = getAttributeValue(timeVar, "units", "seconds since 1970-01-01 00:00:00");
            String calendar = getAttributeValue(timeVar, "calendar", "proleptic_gregorian");
            // 解析基准时间（如 "seconds since 1970-01-01 00:00:00" 中的基准点）
            Date referenceDate = parseReferenceDate(units);

            // 4. 验证时间索引有效性
            int timeSize = timeVar.getShape()[0];
            for (int idx : timeIndexList) {
                if (idx < 0 || idx >= timeSize) {
                    throw new IndexOutOfBoundsException("时间索引 " + idx + " 超出范围（有效范围：0~" + (timeSize - 1) + "）");
                }
            }

            // 5. 确定温度变量的维度顺序
            int[] tempShape = tempVar.getShape();
            int tempDimCount = tempShape.length;
            int timeDimIndex = -1, latDimIndex = -1, lonDimIndex = -1;
            List<Dimension> tempDims = tempVar.getDimensions();
            for (int i = 0; i < tempDims.size(); i++) {
                Dimension dim = tempDims.get(i);
                if (dim.equals(timeVar.getDimension(0))) {
                    timeDimIndex = i;
                } else if (dim.equals(latVar.getDimension(0))) {
                    latDimIndex = i;
                } else if (dim.equals(lonVar.getDimension(0))) {
                    lonDimIndex = i;
                }
            }
            // 校验维度匹配
            if (timeDimIndex == -1 || latDimIndex == -1 || lonDimIndex == -1) {
                throw new IllegalArgumentException("变量 " + val + " 的维度与时间/经纬度不匹配");
            }
            if (tempShape[timeDimIndex] != timeSize) {
                throw new IllegalArgumentException("变量 " + val + " 与时间维度长度不匹配");
            }

            // 6. 处理指定的时间索引数据
            List<String> formattedTimes = new ArrayList<>(timeIndexList.size()); // 格式化后的时间（X轴）
            List<Double> values = new ArrayList<>(timeIndexList.size());        // 对应的值（Y轴）

            // 时间格式化器（输出 yyyy-MM-dd HH:mm:ss）
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

            for (int idx : timeIndexList) {
                // 6.1 读取指定索引的时间值（原始秒数）
                Array timeArray = timeVar.read(new int[]{idx}, new int[]{1});
                double timeSeconds = timeArray.getDouble(0); // 基准时间以来的秒数

                // 6.2 计算实际时间并格式化
                long actualTimeMillis = referenceDate.getTime() + (long) (timeSeconds * 1000);
                Date actualDate = new Date(actualTimeMillis);
                String formattedTime = sdf.format(actualDate);
                formattedTimes.add(formattedTime);

                // 6.3 读取指定索引的温度值
                int[] origin = new int[tempDimCount];
                Arrays.fill(origin, 0);
                origin[timeDimIndex] = idx;       // 指定时间索引
                origin[latDimIndex] = latIndex;   // 固定纬度索引
                origin[lonDimIndex] = lonIndex;   // 固定经度索引
                Array tempArray = tempVar.read(origin, new int[]{1, 1, 1}); // 只读取1个点
                double tempVal = tempArray.getDouble(0);

                // 非NaN值才添加（保留有效数据）
                if (!Double.isNaN(tempVal)) {
                    values.add(tempVal);
                } else {
                    values.add(null); // 或根据需求处理无效值（如跳过）
                }
            }

            // 7. 封装结果
            Map<String, Object> result = new HashMap<>(2);
            result.put("times", formattedTimes); // X轴：格式化后的时间
            result.put("values", values);        // Y轴：对应的值
            return result;
        } catch (ParseException e) {
            throw new IOException("解析时间单位失败", e);
        } catch (InvalidRangeException e) {
            throw new IOException("读取数据范围无效", e);
        }
    }

    // 辅助方法：校验变量非空
    private void validateVariables(Variable... variables) {
        for (Variable var : variables) {
            if (var == null) {
                throw new IllegalArgumentException("变量不存在");
            }
        }
    }

    // 辅助方法：将NetCDF的Array转为double[]
    private double[] extractDoubleArray(Array array) {
        int size = Math.toIntExact(array.getSize());
        double[] result = new double[size];
        IndexIterator iter = array.getIndexIterator();
        for (int i = 0; i < size; i++) {
            result[i] = iter.getDoubleNext(); // 直接读取double值
        }
        return result;
    }

    /**
     * 查找数组中最接近目标值的索引（自动适配升序/降序数组）
     * @param array 有序数组（升序或降序）
     * @param target 目标值
     * @return 最接近目标值的索引
     */
    private int findNearestIndex(double[] array, double target) {
        int length = array.length;
        if (length == 0) {
            return 0; // 空数组返回0（需根据业务需求调整）
        }
        if (length == 1) {
            return 0; // 单元素数组直接返回0
        }

        // 判断数组是升序还是降序
        boolean isAscending = array[0] < array[length - 1];

        // 处理边界情况
        if (isAscending) {
            if (target <= array[0]) {
                return 0;
            }
            if (target >= array[length - 1]) {
                return length - 1;
            }
        } else {
            if (target >= array[0]) {
                return 0;
            }
            if (target <= array[length - 1]) {
                return length - 1;
            }
        }

        // 二分查找核心逻辑
        int left = 0;
        int right = length - 1;
        while (left < right - 1) {
            int mid = (left + right) >>> 1;
            double midValue = array[mid];

            if (midValue == target) {
                return mid; // 精确匹配
            } else if (isAscending) {
                // 升序：目标在右侧
                if (midValue < target) {
                    left = mid;
                } else {
                    right = mid;
                }
            } else {
                // 降序：目标在右侧（值更小的方向）
                if (midValue > target) {
                    left = mid;
                } else {
                    right = mid;
                }
            }
        }

        // 比较左右哪个更接近目标值
        double leftDiff = Math.abs(array[left] - target);
        double rightDiff = Math.abs(array[right] - target);
        return leftDiff <= rightDiff ? left : right;
    }

    // 辅助方法：解析基准时间（从units字符串中提取）
    private Date parseReferenceDate(String units) throws ParseException {
        // units格式示例："seconds since 1970-01-01 00:00:00"
        String[] parts = units.split(" since ");
        if (parts.length != 2) {
            throw new ParseException("无效的时间单位格式: " + units, 0);
        }
        // 支持两种日期格式："yyyy-MM-dd" 或 "yyyy-MM-dd HH:mm:ss"
        String dateStr = parts[1].trim();
        SimpleDateFormat sdf = new SimpleDateFormat(dateStr.contains(":")
                ? "yyyy-MM-dd HH:mm:ss"
                : "yyyy-MM-dd");
        return sdf.parse(dateStr);
    }

    // 辅助方法：获取变量属性值
    private String getAttributeValue(Variable variable, String attrName, String defaultValue) {
        Attribute attr = variable.findAttribute(attrName);
        return attr != null ? attr.getStringValue() : defaultValue;
    }

    /**
     * 使用二分查找查找数组中最接近目标值的索引
     *
     * @param array  有序数组
     * @param target 目标值
     * @return 最接近目标值的索引
     */
    private int binarySearchIndex(Array array, double target) {
        int left = 0;
        int right = (int) array.getSize() - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            double midValue = array.getDouble(mid);
            if (midValue < target) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        // 检查 left 和 left - 1 哪个更接近目标值
        if (left > 0 && Math.abs(array.getDouble(left - 1) - target) < Math.abs(array.getDouble(left) - target)) {
            return left - 1;
        }
        return left;
    }
}
